Skip to content

get/set animation node positions#1206

Open
Thaina wants to merge 4 commits into
CoplayDev:betafrom
Thaina:animation-node-graph-position-MCP
Open

get/set animation node positions#1206
Thaina wants to merge 4 commits into
CoplayDev:betafrom
Thaina:animation-node-graph-position-MCP

Conversation

@Thaina

@Thaina Thaina commented Jun 15, 2026

Copy link
Copy Markdown

Description

adding get_state_positions and set_state_positions to the mcp managed animation controller so that AI can modified the node graph position in addition to create and set its transition

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Test update

Changes Made

Compatibility / Package Source

  • Unity version(s) tested: 6000.3.13f
  • Package source used : file:
  • Resolved commit hash from Packages/packages-lock.json: 4d8cf5d

Testing/Screenshots/Recordings

  • Python tests (cd Server && uv run pytest tests/ -v)
  • Unity EditMode tests
  • Unity PlayMode tests
  • Package import/compile check
  • Not applicable (explain why in Additional Notes)

Documentation Updates

  • I have added/removed/modified tools or resources
  • If yes, I have updated all documentation files using:
    • The LLM prompt at tools/UPDATE_DOCS_PROMPT.md (recommended)
    • Manual review of the generated changes

Related Issues

Additional Notes

Summary by CodeRabbit

  • New Features
    • Added commands to remove animator transitions from a specified source state (optionally filtered by destination).
    • Added commands to fetch and update animator state machine graph state properties (including recursive sub-state-machines) with paging and optional layer filtering.
  • Enhancements
    • Extended controller action routing/validation to support the new get/set state properties actions.
    • Improved Unity object instance ID compatibility across Unity versions, including an editor-only resolver for long instance IDs.
  • Tests
    • Added coverage to verify get/set state properties dispatching, including paging/layer forwarding and preservation of large instance IDs.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Three new static methods are added to ControllerCreate.cs: RemoveTransition deletes outgoing transitions from source states within a layer, while GetStateProperties and SetStateProperties recursively read and write AnimatorController state graph properties (position, speed, motion) across all layers and sub-state-machines using lossless ulong instance IDs. New ulong-compat helpers support lossless ID handling on Unity 6.5+ with graceful fallback on older versions. Both features route through ManageAnimation's controller-action dispatch, register as valid actions in the Python server, and are validated with integration tests.

Changes

AnimatorController Transition and State Property Endpoints

Layer / File(s) Summary
Ulong instance-ID compat helpers
MCPForUnity/Runtime/Helpers/UnityObjectIdCompat.cs
Documentation updated to reference editor-only ulong resolution path. New GetInstanceIDLongCompat() extension returns nullable ulong with version-gated logic: lossless EntityId.ToULong() on Unity 6.5+, unchecked cast on older versions. New editor-only InstanceIDToObjectLongCompat() resolver reconstructs objects using EntityId.FromULong() + EditorUtility.EntityIdToObject() on Unity 6.5+, or delegates to InstanceIDToObjectCompat() via int downcast on older versions.
RemoveTransition endpoint
MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
Adds MCPForUnity.Runtime.Helpers using directive. RemoveTransition loads the AnimatorController, validates fromState and layerIndex, removes outgoing transitions from named source states (or AnyState variants) with optional toState filtering, persists via controller marking/saving, and returns count of removed transitions.
GetStateProperties and SetStateProperties endpoints
MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
GetStateProperties recursively enumerates every AnimatorStateMachine including nested sub-state-machines collecting per-state properties (name, instanceId, layer, x, y, speed, motionInstanceId, motionName, motionType), optionally filters by layerIndex, paginates results with cursor metadata. SetStateProperties validates non-empty states array, builds instanceId-based lookup, records undo, recursively applies updates by matching states (conditionally updating position x/y, updating speed, resolving/clearing motion via motionInstanceId), reassigns sm.states to persist, saves controller, returns matched/unmatched counts and motion resolution failures.
Dispatch routing and action registration
MCPForUnity/Editor/Tools/Animation/ManageAnimation.cs, Server/src/services/tools/manage_animation.py
ManageAnimation adds switch cases routing set_state_properties and get_state_properties to ControllerCreate handlers and updates the unknown-action error message. CONTROLLER_ACTIONS in the Python server gains controller_set_state_properties and controller_get_state_properties for validation and action suggestions.
Integration tests for state property actions
Server/tests/test_manage_animation.py
Expected controller actions list is reformatted across multiple lines. New TestStatePropertyActions class verifies that manage_animation correctly dispatches controller_get_state_properties and controller_set_state_properties and forwards controllerPath, paging fields (page_size, cursor), layerIndex, and state property arrays, with large instanceId values preserved.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 With transitions trimmed and properties swapped,
State machines dance where IDs are mapped,
Recursive depths find nested machines,
Motion and speed paint the animation scenes,
A ulong path where data is wrapped! 🎬

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the main feature additions, includes type of change, compatibility details, and testing checklist. However, it mentions 'get_state_positions' and 'set_state_positions' while the actual implementation added 'GetStateProperties' and 'SetStateProperties' methods (broader scope). Documentation updates checkbox is marked but 'Update Docs' subsection is unchecked. Clarify whether the scope changed from positions-only to broader 'properties' (including speed and motion). Confirm documentation updates were completed as marked, or uncheck if they weren't fully done.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'get/set animation node positions' directly and concisely describes the primary feature additions. It matches the PR's focus on retrieving and modifying animation state node positions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Thaina Thaina force-pushed the animation-node-graph-position-MCP branch from 4d8cf5d to 95b9c2f Compare June 15, 2026 10:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs`:
- Around line 434-443: The GetStatePositions method currently collects and
returns all nodes in a single response without pagination, which violates coding
guidelines for endpoints that could return large datasets. Modify the method to
accept page_size and cursor parameters in the method signature, implement
pagination logic to slice the nodes list collected by CollectPositions based on
these parameters, and update the return object to include a next_cursor field
that indicates the position for the next page of results (or null if this is the
final page). This ensures large AnimatorControllers do not create oversized
payloads.
- Around line 474-481: The foreach loop iterating through positions assumes
every token item is a JObject that supports property indexing, but malformed
entries (nulls, primitives, or incompatible types) will throw exceptions. Before
accessing token["name"], token["x"], and token["y"], add a guard clause to
verify that token is a JObject instance; if not, skip that iteration using
continue or collect the error appropriately. This prevents crashes on malformed
position data while maintaining the existing logic for valid entries.
- Around line 473-503: The SetStatePositions method currently matches states by
name only, which is ambiguous since duplicate names are valid in Animator graphs
and could cause unintended updates across layers. Replace the name-only key in
the want dictionary with a unique state identifier that combines the layer
index, state-machine path, and state name (for example
"layer0/RootStateMachine/StateName"). Update the contract to expect this unique
identifier format instead of just "name", and modify the ApplyPositions method
(and any other matching logic around lines 507-521) to construct and match
against this same unique identifier when traversing the state machine hierarchy,
so that only the intended state is updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10199cd6-3ddb-40ef-aa2e-d236839cdc09

📥 Commits

Reviewing files that changed from the base of the PR and between dccecd6 and 4d8cf5d.

📒 Files selected for processing (3)
  • MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
  • MCPForUnity/Editor/Tools/Animation/ManageAnimation.cs
  • Server/src/services/tools/manage_animation.py

Comment thread MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
Comment thread MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs Outdated
Comment thread MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs (2)

499-506: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard positions items before property indexing to avoid runtime exceptions.

On Line 501–506, the loop indexes token["..."] without verifying object shape. Non-object entries in positions can throw and convert client input errors into internal failures. Add an if (token is not JObject item) continue/collect-error guard before field access.

Minimal fix
 foreach (var token in positions)
 {
-    string name = token["name"]?.ToString();
+    if (token is not JObject item)
+        continue;
+
+    string name = item["name"]?.ToString();
     if (string.IsNullOrEmpty(name))
         continue;
-    float x = token["x"]?.ToObject<float>() ?? 0f;
-    float y = token["y"]?.ToObject<float>() ?? 0f;
-    int? layer = token["layer"]?.ToObject<int>() ?? defaultLayer;
+    float x = item["x"]?.ToObject<float>() ?? 0f;
+    float y = item["y"]?.ToObject<float>() ?? 0f;
+    int? layer = item["layer"]?.ToObject<int>() ?? defaultLayer;
     want[$"{(layer.HasValue ? layer.Value.ToString() : "*")}:{name}"] = new Vector2(x, y);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs` around lines 499 -
506, In the foreach loop that iterates over positions, add a guard clause at the
beginning to verify that each token is a JObject before attempting to access its
properties using the indexer (e.g., token["name"]). If the token is not a
JObject, use continue to skip it or handle the error appropriately. This
prevents runtime exceptions when non-object entries exist in the positions
collection and converts potential client input errors into graceful handling
rather than internal failures.

497-507: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

State key is still ambiguous for duplicate names within the same layer.

On Line 497 and Line 541, matching is keyed by {layer}:{name} (or wildcard), which is not unique across sub-state-machines in that layer. A single request can still move multiple unintended nodes when names repeat. The read/write contract needs a unique identifier (e.g., layer + state-machine path + state name) in both GetStatePositions output and SetStatePositions matching.

Suggested direction
- // Returns [{ name, x, y, layer }]
+ // Returns [{ id, name, x, y, layer, path }]

- want[$"{(layer.HasValue ? layer.Value.ToString() : "*")}:{name}"] = new Vector2(x, y);
+ // Expect `id` from GetStatePositions; use that as the only write key.
+ want[id] = new Vector2(x, y);

- string scopedKey = $"{layer}:{name}";
- string anyKey = $"*:{name}";
+ string scopedKey = BuildStateId(layer, stateMachinePath, name);

Also applies to: 539-545

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs` around lines 497 -
507, The state positioning key format using only layer and name is ambiguous
when duplicate state names exist in different sub-state-machines within the same
layer. Update the key structure to include the state-machine path in addition to
layer and name to ensure uniqueness. In the section around line 497-507 where
the want dictionary is built, modify the key format from {layer}:{name} to
include the full state-machine path (e.g., {layer}:{stateMachinePath}:{name}).
Apply the same key format change in the matching logic around lines 539-545 to
ensure consistency between how positions are stored and retrieved. This requires
coordinating the GetStatePositions output format with the SetStatePositions
matching logic to use the same unique identifier scheme.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs`:
- Around line 499-506: In the foreach loop that iterates over positions, add a
guard clause at the beginning to verify that each token is a JObject before
attempting to access its properties using the indexer (e.g., token["name"]). If
the token is not a JObject, use continue to skip it or handle the error
appropriately. This prevents runtime exceptions when non-object entries exist in
the positions collection and converts potential client input errors into
graceful handling rather than internal failures.
- Around line 497-507: The state positioning key format using only layer and
name is ambiguous when duplicate state names exist in different
sub-state-machines within the same layer. Update the key structure to include
the state-machine path in addition to layer and name to ensure uniqueness. In
the section around line 497-507 where the want dictionary is built, modify the
key format from {layer}:{name} to include the full state-machine path (e.g.,
{layer}:{stateMachinePath}:{name}). Apply the same key format change in the
matching logic around lines 539-545 to ensure consistency between how positions
are stored and retrieved. This requires coordinating the GetStatePositions
output format with the SetStatePositions matching logic to use the same unique
identifier scheme.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2f12fc9-d9c0-495a-9b3f-ef909f5fbfe1

📥 Commits

Reviewing files that changed from the base of the PR and between 95b9c2f and e4fbd3a.

📒 Files selected for processing (2)
  • MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
  • Server/tests/test_manage_animation.py

Thaina added 2 commits June 16, 2026 13:54
Add long entityID for lossless convertion
add remove_transition for editing

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Server/tests/test_manage_animation.py (1)

75-82: ⚡ Quick win

Include controller_remove_transition in the expected controller action contract test.

CONTROLLER_ACTIONS now includes controller_remove_transition, but this expected-set assertion doesn’t check it, so allowlist regressions for that action can slip through.

Suggested test update
     def test_expected_controller_actions_present(self):
         expected = {"controller_create", "controller_add_state",
                     "controller_set_state_properties", "controller_get_state_properties",
-                    "controller_add_transition",
+                    "controller_add_transition", "controller_remove_transition",
                     "controller_add_parameter", "controller_get_info", "controller_assign",
                     "controller_add_layer", "controller_remove_layer", "controller_set_layer_weight",
                     "controller_create_blend_tree_1d", "controller_create_blend_tree_2d", "controller_add_blend_tree_child"}
         assert expected.issubset(set(CONTROLLER_ACTIONS))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Server/tests/test_manage_animation.py` around lines 75 - 82, The
test_expected_controller_actions_present method has an expected set of
controller actions but is missing controller_remove_transition, which is now
part of CONTROLLER_ACTIONS. Add controller_remove_transition to the expected set
in the assertion so that the test will catch if this action is accidentally
removed from CONTROLLER_ACTIONS in the future. Include it in the set of expected
action strings being checked against CONTROLLER_ACTIONS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Server/tests/test_manage_animation.py`:
- Around line 75-82: The test_expected_controller_actions_present method has an
expected set of controller actions but is missing controller_remove_transition,
which is now part of CONTROLLER_ACTIONS. Add controller_remove_transition to the
expected set in the assertion so that the test will catch if this action is
accidentally removed from CONTROLLER_ACTIONS in the future. Include it in the
set of expected action strings being checked against CONTROLLER_ACTIONS.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b812efa-0ba7-47b8-81bb-8bc31580c634

📥 Commits

Reviewing files that changed from the base of the PR and between 85a06b2 and 2f1fbdd.

📒 Files selected for processing (4)
  • MCPForUnity/Editor/Tools/Animation/ControllerCreate.cs
  • MCPForUnity/Editor/Tools/Animation/ManageAnimation.cs
  • Server/src/services/tools/manage_animation.py
  • Server/tests/test_manage_animation.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant